﻿using System;
using UnityEngine;

namespace Obi
{
    [AddComponentMenu("Physics/Obi/Obi Distance Field Renderer", 1003)]
    [ExecuteInEditMode]
    [RequireComponent(typeof(ObiCollider))]
    public class ObiDistanceFieldRenderer : MonoBehaviour
    {
        public enum Axis
        {
            X = 0,
            Y = 1,
            Z = 2,
        }

        public Axis axis;
        [Range(0, 1)]
        public float slice = 0.25f;
        public float maxDistance = 0.5f;

        private ObiCollider unityCollider;
        private Material material;
        private Mesh planeMesh;
        private Texture2D cutawayTexture;

        private float sampleSize;
        private int sampleCount;

        private Color boundsColor = new Color(1, 1, 1, 0.5f);

        public void Awake()
        {
            unityCollider = GetComponent<ObiCollider>();
        }

        public void OnEnable()
        {
            material = GameObject.Instantiate(Resources.Load<Material>("ObiMaterials/DistanceFields/DistanceFieldRendering"));
            material.hideFlags = HideFlags.HideAndDontSave;
        }

        public void OnDisable()
        {
            Cleanup();
        }

        private void Cleanup()
        {
            GameObject.DestroyImmediate(cutawayTexture);
            GameObject.DestroyImmediate(planeMesh);
            GameObject.DestroyImmediate(material);
        }

        private void ResizeTexture()
        {

            if (cutawayTexture == null)
            {
                cutawayTexture = new Texture2D(sampleCount, sampleCount, TextureFormat.RHalf, false);
                cutawayTexture.wrapMode = TextureWrapMode.Clamp;
                cutawayTexture.hideFlags = HideFlags.HideAndDontSave;
            }
            else
                cutawayTexture.Reinitialize(sampleCount, sampleCount);
        }

        private void CreatePlaneMesh(ObiDistanceField field)
        {

            if (field != null && planeMesh == null)
            {

                float uvBorder = (1 - field.FieldBounds.size[0] / (sampleSize * sampleCount)) * 0.5f;

                planeMesh = new Mesh();

                planeMesh.vertices = new Vector3[]{new Vector3(-0.5f,-0.5f,0),
                                                   new Vector3(0.5f,-0.5f,0),
                                                   new Vector3(-0.5f,0.5f,0),
                                                   new Vector3(0.5f,0.5f,0)};

                planeMesh.uv = new Vector2[]{new Vector2(uvBorder,uvBorder),
                                             new Vector2(1-uvBorder,uvBorder),
                                             new Vector2(uvBorder,1-uvBorder),
                                             new Vector2(1-uvBorder,1-uvBorder)};

                planeMesh.normals = new Vector3[] { -Vector3.forward, -Vector3.forward, -Vector3.forward, -Vector3.forward };
                planeMesh.triangles = new int[] { 0, 2, 1, 2, 3, 1 };
            }
        }

        private void RefreshCutawayTexture(ObiDistanceField field)
        {
            if (field == null)
                return;

            Bounds b = field.FieldBounds;
            sampleSize = field.EffectiveSampleSize;
            sampleCount = (int)(b.size[0] / sampleSize) + 1;

            CreatePlaneMesh(field);
            ResizeTexture();

            float sweep = (sampleCount * slice) * sampleSize;
            Vector3 origin = b.center - b.extents;

            for (int x = 0; x < sampleCount; ++x)
                for (int y = 0; y < sampleCount; ++y)
                {
                    Vector3 offset = Vector3.zero;
                    switch (axis)
                    {
                        case Axis.X: offset = new Vector3(sweep, y * sampleSize, x * sampleSize); break;
                        case Axis.Y: offset = new Vector3(x * sampleSize, sweep, y * sampleSize); break;
                        case Axis.Z: offset = new Vector3(x * sampleSize, y * sampleSize, sweep); break;
                    }

                    float distance = ASDF.Sample(field.nodes, origin + offset);

                    float value = ObiUtils.Remap(distance, -maxDistance, maxDistance, 0, 1);

                    cutawayTexture.SetPixel(x, y, new Color(value, 0, 0));
                }
            cutawayTexture.Apply();
        }

        private void DrawCutawayPlane(ObiDistanceField field, Matrix4x4 matrix)
        {

            if (field == null)
                return;

            RefreshCutawayTexture(field);

            material.mainTexture = cutawayTexture;
            material.SetPass(0);

            Quaternion rotation = Quaternion.identity;
            Vector3 offset = Vector3.zero;
            offset[(int)axis] = field.FieldBounds.size[0];

            if (axis == Axis.Y)
                rotation = Quaternion.Euler(90, 0, 0);
            else if (axis == Axis.X)
                rotation = Quaternion.Euler(0, -90, 0);

            Matrix4x4 sc = Matrix4x4.TRS(field.FieldBounds.center + offset * (slice - 0.5f), rotation, Vector3.one * field.FieldBounds.size[0]);
            Graphics.DrawMeshNow(planeMesh, matrix * sc);

        }

        public void OnDrawGizmos()
        {
            if (unityCollider != null && unityCollider.distanceField != null && unityCollider.distanceField.Initialized && material != null)
            {
                DrawCutawayPlane(unityCollider.distanceField, transform.localToWorldMatrix);
                Gizmos.color = boundsColor;
                Gizmos.DrawWireCube(unityCollider.distanceField.FieldBounds.center, unityCollider.distanceField.FieldBounds.size);
            }
        }

    }
}

